#include "stdafx.h"
#include <boost/test/unit_test.hpp>
#include "detail/bmock_test_mode_control.hpp"
#include <boost/test/execution_monitor.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <set>
#include <ctime>
#include <boost/logic/tribool.hpp>

namespace bmock {
	struct bmock_test_timing {
		bmock_test_timing():start_time(clock()) {}
		~bmock_test_timing() {
			bmock_mutex::instance()->destruct();
			if (one_bmock_test)
			{
				std::cout << "\nUnit tests timing: " 
					<< double(clock()-start_time)/CLOCKS_PER_SEC 
					<< " seconds\n";
			}
		}

		static void set_was_one_test() { one_bmock_test = true; }
	private:
		static bool one_bmock_test;
		clock_t start_time;
	};


	bool bmock_test_timing::one_bmock_test = false;
	static bmock_test_timing b;

	const result_ptr test_mode_control::p_void_ = result_ptr(new void_result);
	std::stack<test_mode_control *> test_mode_control::instances_;

	test_mode_control::test_mode_control(const location &loc,const control_tools_ptr &t)
	{
		tools_=t;
		typed_pointers_=true;
		init(loc);
	}

	void test_mode_control::init(const location &loc) {
		loc_ = loc;
		state_test::init(this); 
		instances_.push(this);
	}
	test_mode_control::~test_mode_control(){
		state_->terminate(this);
		instances_.pop();
		if (instances_.empty()) {
			bmock_mutex::instance()->destruct();
		}
	}
	void test_mode_control::replay(const location &loc) {
		loc_ = loc;
		state_->replay(this);
	}
	void test_mode_control::verify(const location &loc) {
		loc_ = loc;
		state_->verify(this);
	}
	test_mode_control* test_mode_control::get_instance(){
		if (instances_.empty())
			return NULL;

		bmock_test_timing::set_was_one_test();
		return instances_.top();
	}

	bool test_mode_control::terminate() {
		return state_->check_terminate(this);
	}

	void test_mode_control::set_result(const location &loc,const result *const r) {
		loc_ = loc;
		p_result_ = result_ptr(r);
		state_->set_result(this);
	}

	void test_mode_control::set_void_result(const location &loc) {
		loc_ = loc;
		p_result_ = p_void_;
		state_->set_result(this);
	}

	void test_mode_control::expect_call_range(int min_, const bmock::infinite_calls&) {
		expect_call_range(min_,min_);
		last_expectation_stub_->set_infinite_calls();
	}
	void test_mode_control::expect_call_range(int min_, int max_) {
		if (min_ < 0 || min_ > max_ || (min_ == max_ && max_ == 0) ) {
			state_->error(this, INVALID_CALL_TIMES_RANGE);
		}
		last_expectation_stub_->expect_call_times_range(min_,max_);
	}

	void test_mode_control::check(const bmock::func_spec &f, const bmock::arg_list &args){
		bmock_mutex::instance()->lock(); 
		state_->check(this,f,args);
	}

	void test_mode_control::return_void(const location &loc){
		state_->return_void(this,loc);
		bmock_mutex::instance()->unlock(); 
	}

	void test_mode_control::bad_return_type() {
		state_->error(this,BAD_RETURN_TYPE);
	}

	void test_mode_control::set_stub_recording_state()
	{
		state_->set_stub_recording_state(this);
	}

	void test_mode_control::set_non_strict_recording_state()
	{
		state_->set_non_strict_recording_state(this);
	}

	void test_mode_control::validate_expect() {
		state_->validate_expect(this);
	}

	void test_mode_control::connect(const expectation::slot_type &slot,const location &loc) {
		loc_ = loc;
		state_->connect(this,slot);
	}

	void test_mode_control::add_mock_type(boost::shared_ptr<mocks_type> mt) {
		if (!mocked_area_types_.empty() && mocked_area_types_.back()->empty()) {
			mocked_area_types_.pop_back();
		}
		mocked_area_types_.push_back(mt);
	}

	bmock_any test_mode_control::get_result_value() const { 
		return p_result_->get_value(); 
	}
	void test_mode_control::set_location(const char * const f,const size_t l) {
		loc_ = location(f,l);
	}

	void test_mode_control::add_expectation() {
		if (mocked_area_types_.empty()) {
			mocked_area_types_.push_back(boost::shared_ptr<mocks_type>(new strict_mocks()));
		}
		mocked_area_types_.back()->add_expectation(exp_ptr(new expectation(tools_)));	
	}

	void test_mode_control::add_stub() {
		new_stub_=exp_ptr(new expectation(tools_));
	}

	void test_mode_control::add_non_strict() {
		non_strict_calls_.add_expectation(exp_ptr(new expectation(tools_)));
	}

	void test_mode_control::not_received() {
		//check area mocks (mocks not received)
		while (!mocked_area_types_.empty()) {
			while (!mocked_area_types_.front()->empty()) {
				mocked_area_types_.front()->not_received();
				mocked_area_types_.front()->pop_last();
			}
			mocked_area_types_.pop_front();
		}	

		//check non strict mocks (mocks not received)
		while (!non_strict_calls_.empty()) {
			non_strict_calls_.not_received();
			non_strict_calls_.pop_last();
		}
	}

	void test_mode_control::next_line() {
		++loc_.second;
	}

	void test_mode_control::prev_line() {
		--loc_.second;
	}

	void test_mode_control::check_call(const func_spec &f,const arg_list &args) {		
		//when no calls suppose to be made
		if(is_empty()) {		//areas queue is empty
			std::pair<exp_ptr,boost::logic::tribool> p_e_pair_non_strict(exp_ptr(),false);
			try {
				p_e_pair_non_strict = non_strict_calls_.check(f, args);
				if (p_e_pair_non_strict.second) {
					post_check_ops(p_e_pair_non_strict.first, &non_strict_calls_,f, args, false);
					return;
				}
			}
			catch (...) {}
			if (!p_e_pair_non_strict.second) {	//if 'f' doesn't match even by name, check if stub. otherwise stub overwritten
				try
				{
					check_stub_call(f,args);	//if it's a stub call, it's ok
					return;
				}
				catch (...) {}			
			}
			unexpected_call(f,args);	
		}

		std::pair<exp_ptr,boost::logic::tribool> p_e_pair;
		try {
			p_e_pair = mocked_area_types_.front()->check(f,args);
		}
		catch (...) {		//if mocked_area_types_.front() is done, check next area
			mocked_area_types_.pop_front();
			check_call(f, args);
			return;
		}

		if (!p_e_pair.second || boost::logic::indeterminate(p_e_pair.second))  {	//not found in area mocks
			std::pair<exp_ptr,boost::logic::tribool> p_e_pair_non_strict(exp_ptr(), false);
			try {
				p_e_pair_non_strict = non_strict_calls_.check(f, args);
				if (p_e_pair_non_strict.second) {
					post_check_ops(p_e_pair_non_strict.first, &non_strict_calls_, f, args, false);	
					return;
				}
			}
			catch (...) {}
			if (!p_e_pair.second && !p_e_pair_non_strict.second) {
				try {
					check_stub_call(f, args);	//if 'f' not non_strict and 'f' doesn't match by name, check stub
					return;
				}
				catch (...){}
			}
			exp_ptr p_e = (p_e_pair.second != false || 
				p_e_pair_non_strict.first.get() == NULL) ? p_e_pair.first : p_e_pair_non_strict.first;
			post_check_ops(p_e, mocked_area_types_.front().get(), f, args, true);
			return;
		}

		post_check_ops(p_e_pair.first, mocked_area_types_.front().get(), f, args, true);
	}

	void test_mode_control::post_check_ops(exp_ptr p_e, mocks_type* mock_type_ds_ptr,
		const func_spec& f, const arg_list& args, bool call_in_mock_area) 
	{
		if (p_e->is_last_call()) {
			mock_type_ds_ptr->pop_last();	
			if (call_in_mock_area && mock_type_ds_ptr->empty()) {
				mocked_area_types_.pop_front();
			}
		}
		else {
			p_e->add_actual_call();
		}
		p_result_ = p_e->check(f,args);
	}

	void test_mode_control::check_stub_call(const func_spec &f,const arg_list &args){
		std::map<location,exp_ptr>::iterator iter=stub_calls_.find(f.get_loc());
		if(iter!=stub_calls_.end())
		{
			p_result_ = iter->second->check_stub(f,args);
			return;
		}
		throw loc_;
	}

	void test_mode_control::expect_call(const func_spec &f,const arg_list &args) { 
		mocked_area_types_.back()->expect_call(f,loc_);
		try
		{
			mocked_area_types_.back()->expect_arguments(args);
		}
		catch (std::string m)
		{
			if(m!=message(INVALID_IN_RAW_MEM))
				throw m;
			tools_->error(m,loc_);
		}

		last_expectation_stub_ = mocked_area_types_.back()->get_latest();
	}

	void test_mode_control::expect_stub_call(const func_spec &f,const arg_list &args) { 

		std::pair<std::map<location,exp_ptr>::iterator, bool>  a;	

		a=stub_calls_.insert(std::make_pair(f.get_loc(),new_stub_));
		if(!a.second)
		{
			tools_->error(message(INVALID_STUB_SAME_FUNC),loc_);
		}

		a.first->second->expect_call(f,loc_);
		a.first->second->expect_arguments(args);
		last_expectation_stub_=a.first->second;
	}
	void test_mode_control::expect_non_strict_call(const func_spec &f,const arg_list &args) { 
		non_strict_calls_.expect_call(f, f.get_loc());
		non_strict_calls_.expect_arguments(args);
		last_expectation_stub_ = non_strict_calls_.get_latest();
	}

	bool test_mode_control::is_done() const {
		std::list<boost::shared_ptr<mocks_type> >::const_iterator it;
		for (it = mocked_area_types_.begin(); it != mocked_area_types_.end(); ++it) {
			if (!(*it)->is_done()) {
				return false;
			}
		}
		if (!non_strict_calls_.is_done()) {
			return false;
		}

		return true;
	}

	bool test_mode_control::is_empty() const { 
		return mocked_area_types_.empty(); 
	}

	bool test_mode_control::stub_is_empty() const{
		return stub_calls_.empty();
	}

	bool test_mode_control::non_strict_is_empty() const{
		return non_strict_calls_.empty();
	}

	void test_mode_control::save_result() {
		mocked_area_types_.back()->set_result(p_result_);
	}

	void test_mode_control::save_stub_result() {
		new_stub_->set_result(p_result_);
	}

	void test_mode_control::save_non_strict_result() {
		non_strict_calls_.set_result(p_result_);
	}

	void test_mode_control::connect_callback(const expectation::slot_type &slot) {
		last_expectation_stub_->connect(slot);
	}

	void test_mode_control::unexpected_call(const func_spec &f,const arg_list &args) {
		tools_->error(f.message(args),loc_);
	}
	void test_mode_control::unexpected_call_before_replay(const func_spec &f,const arg_list &args)
	{
		tools_->error(f.message(args)+"(m.b. BMOCK_REPLAY is missing)",loc_);
	}

	void test_mode_control::state_error(bmock::error_code ec,const location &loc) {
		tools_->error(message(ec),loc);
	}

	void test_mode_control::state_error(bmock::error_code ec) {
		tools_->error(message(ec),loc_);
	}
	void test_mode_control::use_void_ptrs_only() {
		typed_pointers_ = false;
	}

	void test_mode_control::abort(const location &l) const {
		tools_->abort(l);
	}

	void test_mode_control::set_state(const state_test *const st) { 
		state_=st; 
	}


	inline bool is_a_match(std::string method,boost::regex x)
	{
		return boost::regex_match(method,x);
	}

	bool test_mode_control::mock_defined(const std::string& v) const
	{
		std::vector<boost::regex>::const_iterator find_;	
		find_=find_if(mocks_.begin(),mocks_.end(),boost::lambda::bind(is_a_match,v,boost::lambda::_1));
		return !(find_==mocks_.end());
	}

	void test_mode_control::define_mock(const std::string& v)
	{
		if (is_exact_mock(v))
			insert_exact_mock(v);
		else
			mocks_.push_back(turn_to_regex(v));
	}

	boost::regex test_mode_control::turn_to_regex(std::string v)
	{
		if(v[v.size()-1]=='*')
		{
			v[v.size()-1]='.';
			v.append("*");
		}
		boost::regex reg(v);
		return reg;
	}

	bool test_mode_control::is_exact_mock(const std::string& str) const {
		return (str.find("\"")!=std::string::npos);
	}

	void test_mode_control::insert_exact_mock(std::string str){
		str.erase(0,1);
		str.erase(str.length()-1);
		try
		{
			boost::replace_all(str, "__cdecl ", "");
			boost::replace_all(str, "__thiscall ", "");
			exact_mocks_.push_back(str);
		}
		catch (bmock::mock_dec_exception e)
		{
			get_tools()->error(message(INVALID_MOCK_FUNCTION_DECLARATION),loc_);
		}

	}

	bool test_mode_control::exact_mock_defined(std::string func_decl) const {
		boost::replace_all(func_decl, "__cdecl ", "");
		boost::replace_all(func_decl, "__thiscall ", "");
		std::vector<std::string>::const_iterator find_;

		find_= find(exact_mocks_.begin(),exact_mocks_.end(), func_decl);
		return !(find_==exact_mocks_.end());
	}

	bmock_any test_mode_control::get_new_result() {
		try {
			bmock_any res = state_->get_result(this);
			bmock_mutex::instance()->unlock();
			return res;
		} catch(boost::execution_aborted) {
			bmock_mutex::instance()->unlock();
			return bmock_any();
		}
	}	

	static bool IsThis(const argument& arg)
	{
		return arg.get_name() == "this";
	}

	void test_mode_control::compare_args_to_func_sig(const std::vector<bmock::argument>& macro_args, const std::vector<std::string>& func_args, const location& loc) const
	{
		static std::set<location> checked_locations_;	

		if ( checked_locations_.insert(loc).second == false )
			return;

		std::set<std::string> args_set;
		for (size_t i = 0; i < macro_args.size(); i++)
		{
			if ( args_set.insert(macro_args[i].get_name()).second == false)
				tools_->warn(message(ARGUMENT_APPEARS_MORE_THAN_ONCE, macro_args[i].get_name()), loc);				
		}

		size_t macro_args_size = macro_args.size();
		if ( std::find_if(macro_args.begin(), macro_args.end(), IsThis) != macro_args.end() )
			macro_args_size--;

		if ( macro_args_size != func_args.size() )
			tools_->warn_not_equal(macro_args_size, func_args.size(), message(ARGUMENTS_NUM_NOT_EQUAL), loc);		
	}
	
	boost::any* test_mode_control::store_ret_val_data(boost::any v)
	{		
		static std::list<boost::any> mem_storage_list;
		static size_t current_test_id = 0;

		if ( current_test_id != boost::unit_test::framework::current_test_case().p_id )
		{
			current_test_id = boost::unit_test::framework::current_test_case().p_id;
			mem_storage_list.clear();
		}

		mem_storage_list.push_back(v);
		return &mem_storage_list.back();
	}
}